﻿using System;
using System.Collections.Generic;

namespace WS.EKA.Utilities.Helpers
{
    public class StrongQuickEnum<T>
    {
        static StrongQuickEnum()
        {
            _t = typeof(T);
            string[] names = Enum.GetNames(_t);
            _strToEnum = new Dictionary<string, T>(names.Length);
            _strToEnumIgnoreCase = new Dictionary<string, T>(names.Length);
            _intToEnum = new Dictionary<int, T>(names.Length);
            foreach (string name in names)
            {
                T enumObject = (T)Enum.Parse(_t, name);
                _strToEnum.Add(name, enumObject);
                _strToEnumIgnoreCase.Add(name.ToLower(), enumObject);
                int enumInt = Convert.ToInt32(enumObject);
                _intToEnum.Add(enumInt, enumObject);
            }
        }
        /// <summary>
        /// Will serve us in the Parse method
        /// </summary>
        private static Dictionary<string, T> _strToEnum;
        /// <summary>
        /// Will serve us in the Parse method, with ignoreCase == true.
        /// It is possible not to hold this member and to define a Comparer class -
        /// But my main goal here is the performance at runtime.
        /// </summary>
        private static Dictionary<string, T> _strToEnumIgnoreCase;
        /// <summary>
        /// Will serve us in the ToObject method
        /// </summary>
        private static Dictionary<int, T> _intToEnum;
        private static Type _t;
        public static T Parse(string value)
        {
            return _strToEnum[value]; // Exception will be thrown if the value is not found
        }
        public static T Parse(string value, bool ignoreCase)
        {
            if (ignoreCase)
            {
                string valLower = value.ToLower();
                return _strToEnumIgnoreCase[valLower];
            }
            else
                return Parse(value);
        }
        public static T ToObject(object value)
        {
            try
            {
                int intval = (int)value;
                return ToObject(intval);
            }
            catch (InvalidCastException)
            {
                throw new ArgumentException("Cannot convert " + value + " to int");
            }
            //If an exception is coming from ToObject(intval), do not catch it here.
        }
        public static T ToObject(int value)
        {
            return _intToEnum[value];
        }
        public static string GetName(object value)
        {
            // We can hold an additional dictionary to map T -> string.
            // In my specific usage, this usages is rare, 
            // so I selected the lower performance option
            try
            {
                T valueT = (T)value;
                foreach (KeyValuePair<string, T> pair in _strToEnum)
                {
                    int x = Convert.ToInt32(pair.Value);
                    if (pair.Value.Equals(valueT))
                        return pair.Key;
                }
            }
            catch
            {
                throw new ArgumentException("Cannot convert " + value +
                            " to " + _t.ToString());
            }
            return null; // should never happen
        }
        public static string[] GetNames()
        {
            // .NET 3.5:
            // use the magic _strToEnum.Keys.ToArray() and that's it!
            string[] res = new string[_strToEnum.Count];
            int i = 0;
            foreach (string str in _strToEnum.Keys)
                res[i++] = str;
            return res;
        }
        public static Array GetValues()
        {
            Array res = Array.CreateInstance(_t, _strToEnum.Count);
            int i = 0;
            foreach (T enumObject in _strToEnum.Values)
                res.SetValue(enumObject, i++);
            return res;
        }
        public static bool IsDefined(object value)
        {
            try
            {
                int intval = (int)value;
                return _intToEnum.ContainsKey(intval);
            }
            catch
            {
                return false;
            }
        }
        public static Type GetUnderlyingType()
        {
            // Seems like this is good enough.
            return Enum.GetUnderlyingType(_t);
        }
    }
}
